﻿using System;
using System.Collections.Generic;
using System.Linq;
using MediaController.Client.DAL;
using MediaController.Subs;
using MediaController.Subs.Provider;
using MediaController.Utils.Listener;
using System.IO;
using System.Text.RegularExpressions;
using MediaController.Utils;

namespace MediaController.Watcher
{
    public class Watcher
    {
        private static readonly object _synceObject = new object();
        private static Watcher _instance;
        private readonly List<WatchAdapter> _watchFolders;
        SratimProvider _sratimProvider = new SratimProvider();
        public static Watcher Intance
        {
            get
            {
                lock (_synceObject)
                {
                    if (_instance == null)
                    {
                        _instance = new Watcher();
                    }
                }
                return _instance;
            }
        }

        public bool Started { get; private set; }

        public Watcher()
        {
            Started = false;
            _watchFolders = new List<WatchAdapter>();
            //_sratimProvider.SearchedFileChanged += _sratimProvider_ProgressChanged;
        } 

        public virtual void StartAsync()
        {
            var listeners = DataAccess.Instance.MediaControllerDataSet.Listeners;
            foreach (var listener in listeners)
            {
                var watchFolder = new ArchivesWatcher(listener);
                watchFolder.FileFouned += WatchFolderFileFouned;
                watchFolder.ExtractCompleted += WatchFolderExtractCompleted;
                watchFolder.ExtractingFile += WatchFolderExtractingFile;

                if (listener.Enabled)
                    watchFolder.Start();

                _watchFolders.Add(watchFolder);
            }
            Started = true;
        }

        public void Stop()
        {
            foreach (var listener in _watchFolders)
            {
                var archivesWatcher = (ArchivesWatcher)listener;

                if (archivesWatcher.ListenersRow.Enabled)
                    listener.Stop();

                archivesWatcher.ListenersRow.Enabled = false;
            }
        }

        public void Pause()
        {
            foreach (var listener in _watchFolders)
            {
                var archivesWatcher = (ArchivesWatcher)listener;

                if (archivesWatcher.ListenersRow.Enabled)
                    listener.Stop();
            }
        }

        public void Resume()
        {
            foreach (var listener in _watchFolders)
            {
                var archivesWatcher = (ArchivesWatcher)listener;

                if (archivesWatcher.ListenersRow.Enabled)
                    listener.Start();
            }
        }

        #region Private Methods

        void WatchFolderFileFouned(object sender, FondedFileEventArgs obj)
        {
            try
            {
                if (IsInList(obj.FileName))
                {
                    obj.Cancel = true;
                    return;
                }

                if (Regex.Match(obj.FileName, @"^.*\.rar$|^*.001", RegexOptions.IgnoreCase).Success)
                {
                    Logger.Log(string.Format("Found file: {0}", obj.FileName));
                    SetRowStatus(obj.FileName, (int)eFileStatus.New);
                }

                else if (Regex.Match(obj.FileName, @"^.*\.mkv$|^*.avi", RegexOptions.IgnoreCase).Success && !obj.FileName.ToLower().Contains("sample"))
                {
                    Logger.Log(string.Format("Found file: {0}", obj.FileName));
                    var filesRow = SetRowStatus(obj.FileName, (int)eFileStatus.New);
                    List<string> destinations = MoveFilesFromFolder(Path.GetDirectoryName(obj.FileName), "sample");

                    if (destinations != null && destinations.Count > 0)
                    {
                        foreach (var destination in destinations)
                        {
                            DownloadSubtitles(destination);
                        }
                        filesRow.Destination = destinations[0];
                    }

                    SetRowStatus(obj.FileName, (int)eFileStatus.Done);
                }
            }
            catch (Exception ex)
            {
                Logger.Log(ex.ToString());
            }
        }

        void WatchFolderExtractingFile(object arg1, ExtractingFileEventArgs obj)
        {
            try
            {
                Logger.Log(string.Format("Extracting file: {0}", obj.FileName));
                SetRowStatus(obj.FileName, (int)eFileStatus.Active);
            }
            catch (Exception ex)
            {
                Logger.Log(ex.ToString());
            }
        }

        void WatchFolderExtractCompleted(object sender, DoneExtractingEventArgs obj)
        {
            try
            {
                Logger.Log(string.Format("Done extracting file: {0}", obj.FileName));

                List<string> destinations = null;
                var archivesWatcher = (ArchivesWatcher)sender;


                if (archivesWatcher.ListenersRow.Move)
                    destinations = MoveFilesFromFolder(obj.ExtractedDirectory);

                if (!archivesWatcher.ListenersRow.IsDeleteAfterExtractNull() && archivesWatcher.ListenersRow.DeleteAfterExtract)
                    DeleteEmptyFolder(obj.ExtractedDirectory);

                if (destinations != null && destinations.Count > 0)
                {
                    foreach (var destination in destinations)
                    {
                        DownloadSubtitles(destination);
                    }
                    var filesRow = SetRowStatus(obj.FileName, (int)eFileStatus.Done);
                    filesRow.Destination = destinations[0];
                }
                SetRowStatus(obj.FileName, (int)eFileStatus.Done);
            }
            catch (Exception ex)
            {
                
                Logger.Log(ex.ToString());
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        private void DownloadSubtitles(string path)
        {
            try
            {
                _sratimProvider.DownloadSubtitle(path,SearchOption.TopDirectoryOnly);
            }
            catch (Exception ex)
            {
                Logger.Log(ex.ToString());
            }
        }

        /// <summary>
        /// Check if file is already in list
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns></returns>
        private bool IsInList(string fileName)
        {
            var filesRow = DataAccess.Instance.MediaControllerDataSet.Files.FindByFullPath(fileName);
            return filesRow != null;
        }

        /// <summary>
        /// Gets all files from the given folder and try to move them to the right categry directory
        /// </summary>
        /// <param name="path"></param>
        /// <param name="ignoreFileWithText">will ignore files that contains specific strings</param>
        /// <returns></returns>
        private List<string> MoveFilesFromFolder(string path, params string[] ignoreFileWithText)
        {
            if (!Directory.Exists(path)) return null;

            var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);
            var destinations = new List<string>();
            foreach (var file in files)
            {
                try
                {
                    if (ContainsText(file, ignoreFileWithText)) continue;

                    if (Regex.Match(file, @"^.*\.mkv$|^*.avi$|^.*\.nfo$|^.*\.srt$", RegexOptions.IgnoreCase).Success)
                        destinations.Add(MoveFoundedFile(file));
                }
                catch (Exception)
                {
                    Logger.Log(string.Format("Could not find path match to file name {0}", file));
                }
            }

            //Delete empty folders or folders with ignored files
            bool delete = true;
            string[] filesToCheck = Directory.GetFiles(path);
            foreach (var file in filesToCheck)
            {
                if (!ContainsText(file, ignoreFileWithText))
                {
                    delete = false;
                    break;
                }
            }
            if (delete) Directory.Delete(path);

            return destinations;
        }

        private bool ContainsText(string file, params string[] ignoreFileWithText)
        {
            return ignoreFileWithText.Any(textToIgnore => file.ToLower().Contains(textToIgnore.ToLower()));
        }

        /// <summary>
        /// Delete the given folder if empty
        /// </summary>
        /// <param name="path"></param>
        private void DeleteEmptyFolder(string path)
        {
            if (!Directory.Exists(path)) return;
            if (Directory.GetFiles(path).Length == 0 && Directory.GetDirectories(path).Length == 0)
            {
                Directory.Delete(path);
            }
        }

        /// <summary>
        /// Move file to the right category destination directory
        /// </summary>
        /// <param name="fileName"></param>
        /// <returns>file new path</returns>
        private string MoveFoundedFile(string fileName)
        {
            int? categoryId = FindCategory(fileName);
            if (categoryId == null) return "Cannot detrmine category";

            string destination = GetDestination(categoryId.Value);
            if (string.IsNullOrEmpty(destination)) return "Cannot find destination";

            destination = BuildDestinationPath(destination, fileName, categoryId.Value);
            string dir = Path.GetDirectoryName(destination);

            if (!Directory.Exists(dir))
                Directory.CreateDirectory(dir);

            FileSystemUtils.MoveFile(fileName, destination, true);

            return dir;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="destination"></param>
        /// <param name="fullFileName"></param>
        /// <param name="categoryId"></param>
        /// <returns></returns>
        private string BuildDestinationPath(string destination, string fullFileName, int categoryId)
        {
            string retDestination = destination;
            string fileName = Path.GetFileName(fullFileName);
            string mediaName = string.Empty;

            if (destination.Contains("<MediaName>"))
                mediaName = GetMediaName(fullFileName, categoryId);

            string directoryName = Path.GetDirectoryName(fullFileName);
            directoryName = directoryName.Replace("\\Extracted", "");
            int index = directoryName.LastIndexOf(@"\");
            directoryName = directoryName.Substring(index + 1, (directoryName.Length - 1) - index);

            retDestination = retDestination.Replace("<MediaName>", mediaName);
            retDestination = retDestination.Replace("<Directory>", directoryName);
            retDestination = retDestination.Replace("<FileName>", fileName);

            return retDestination;
        }

        /// <summary>
        /// Extract the media name from file name
        /// </summary>
        /// <param name="fullFileName">file full name</param>
        /// <param name="categoryId">the media category</param>
        /// <returns>media name if succeded. null otherwise</returns>
        private string GetMediaName(string fullFileName, int categoryId)
        {
            string fileName = Path.GetFileName(fullFileName);
            string mediaName = null;

            var regexRows = DataAccess.Instance.GetPrioritizeRegex(eRegexType.SearchPattern, categoryId);
            foreach (var regexRow in regexRows)
            {
                var match = Regex.Match(fileName, regexRow.Regex, RegexOptions.IgnoreCase);

                if (match.Success)
                {
                    mediaName = match.Groups["MediaName"].Value;
                    break;
                }
                else
                {
                    string dirName = Path.GetDirectoryName(fullFileName);
                    dirName = dirName.Replace("\\Extracted", "");
                    var lastIndex = dirName.LastIndexOf('\\');
                    dirName = dirName.Substring(lastIndex + 1, dirName.Length - 1 - lastIndex);

                    match = Regex.Match(dirName, regexRow.Regex, RegexOptions.IgnoreCase);

                    if (match.Success)
                    {
                        mediaName = match.Groups["MediaName"].Value;
                        break;
                    }
                }
            }
            if (mediaName != null)
            {
                //Upper case first chars in the media name
                mediaName = mediaName.Replace('.', ' ');
                string tempName = null;
                foreach (var splitedName in mediaName.Split(' '))
                {
                    tempName = string.Format("{0} {1}", tempName, UppercaseFirst(splitedName));
                }
                if (tempName != null) mediaName = tempName.Trim();
            }
            return mediaName;
        }

        static string UppercaseFirst(string s)
        {
            // Check for empty string.
            if (string.IsNullOrEmpty(s))
            {
                return string.Empty;
            }
            // Return char and concat substring.
            return char.ToUpper(s[0]) + s.Substring(1);
        }

        /// <summary>
        /// Get the category id from the file name
        /// </summary>
        /// <param name="fileName">file to search category</param>
        /// <returns>categoryid if succeded, null otherwise</returns>
        private int? FindCategory(string fileName)
        {
            var regexRows = DataAccess.Instance.GetPrioritizeRegex(eRegexType.Determine);
            foreach (var regexRow in regexRows)
            {
                var match = Regex.Match(fileName, regexRow.Regex);
                if (match.Success)
                {
                    return regexRow.CategoryId;
                }
            }
            return null;
        }

        /// <summary>
        /// Get the destination template for the given category
        /// </summary>
        /// <param name="categoryId"></param>
        /// <returns></returns>
        private string GetDestination(int categoryId)
        {
            var destinationsRows = (MediaControllerDataSet.DestinationsRow[])DataAccess.Instance.MediaControllerDataSet.Destinations.Select(string.Format("CategoryId = {0}", categoryId));
            return destinationsRows.Length > 0 ? destinationsRows[0].Destination : null;
        }

        /// <summary>
        /// find the row using the file name, create new row if not exists
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        private MediaControllerDataSet.FilesRow SetRowStatus(string fileName, int status)
        {
            var filesRow = DataAccess.Instance.MediaControllerDataSet.Files.FindByFullPath(fileName);
            if (filesRow == null)
            {
                filesRow = DataAccess.Instance.MediaControllerDataSet.Files.NewFilesRow();
                filesRow.StatusId = status;
                filesRow.FileName = Path.GetFileName(fileName);
                filesRow.FullPath = fileName;
                if (status == -4)
                    filesRow.EndTime = DateTime.Now;

                DataAccess.Instance.MediaControllerDataSet.Files.AddFilesRow(filesRow);
            }
            else
            {
                if (status == -3)
                    filesRow.StartTime = DateTime.Now;
                else if (status == -4)
                    filesRow.EndTime = DateTime.Now;
                filesRow.StatusId = status;
            }
            return filesRow;
        }

        #endregion
    }
}
